/*
 *  sokoban - a Sokoban game for midp-supporting mobile devices
 *  Copyright (C) 2007,2009 Dedi Hirschfeld
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


package com.xomzom.midlets.sokoban;

import java.io.IOException;
import java.util.Vector;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;


/**
 * The Sokban game Midlet.
 * 
 * @author Dedi Hirschfeld
 */
public class Game extends MIDlet implements CommandListener
{
    //
    // Constants.
    //
    
    /**
     * The record store name.
     */
    private final static String RECORD_STORE_NAME = Game.class.getName();

    /**
     * The 'about' view contents file.
     */
    private static final String ABOUT_FILE = "/doc/about.txt";

    /**
     * The 'about' view contents file.
     */
    private static final String ABOUT_TITLE = "Welcome to Sokoban";
   
    /**
     * The 'GPL' view contents file.
     */
    private static final String LICENSE_FILE = "/doc/license.txt";

    /**
     * The 'about' view contents file.
     */
    private static final String LICENSE_TITLE = "GNU Public License ver. 2";
   

    //
    // Members.
    //
    
    /**
     * The current level.
     */
    private int m_level = 1;
    
    /**
     * The game board.
     */
    private Board m_board = new Board();
    
    /**
     * The game canvas.
     */
    private SokoCanvas m_canvas = new SokoCanvas(this);

    /**
     * The game display.
     */
    private Display m_display;
    
    //
    // Game commands.
    //
    
    /**
     * Command for next level.
     */
    private Command m_cmdNext;

    /**
     * Command for prev level.
     */
    private Command m_cmdPrev;

    /**
     * Command for select level.
     */
    private Command m_cmdSelectLevel;

    /**
     * Command for undo move.
     */
    private Command m_cmdUndo;

    /**
     * Command for 'restart' operation.
     */
    private Command m_cmdRestart;

    /**
     * Command for 'exit' operation.
     */
    private Command m_cmdExit;

    /**
     * Command for 'about' operation.
     */
    private Command m_cmdAbout;

    /**
     * Command for 'show license' operation.
     */
    private Command m_cmdLicense;

    /**
     * The 'select level' dialog.
     */
    private SelectLevelDialog m_selectLevelDialog;
    
    /**
     * The list of moves done in the game.
     */
    private Vector m_moveList = new Vector();
    
    
    //
    // Operations.
    //

    /**
     * Start the game.
     */
    protected void startApp() throws MIDletStateChangeException
    {
        m_display = Display.getDisplay(this);
        init();
    }
    
    /**
     * Cleanup and exit.
     */
    protected void destroyApp(boolean arg0) throws MIDletStateChangeException
    {
        cleanup();
    }

    protected void pauseApp()
    {
    }
    
    /**
     * Display the given error alert, and exit when the user clicks OK. This
     * will not call 'cleanup' before exiting.
     */
    private void alertAndExit(String title, String text)
    {
        Alert alert = 
            new Alert(title, text, null, AlertType.ERROR);
        alert.setTimeout(Alert.FOREVER);
        alert.setCommandListener(new CommandListener(){

            public void commandAction(Command c, Displayable d)
            {
                notifyDestroyed();
            }
        });
        m_display.setCurrent(alert);
    }

    /**
     * Initialize the game.
     */
    private void init()
    {
        readLevel();
        try
        {
            m_board.read(m_level);
        }
        catch (IOException e)
        {
            e.printStackTrace();
            alertAndExit("Can't load board", 
                        "Can't load board " + m_level + ", exiting");
            // We need a return here, because even though the alert is 
            // displayed, the thread that called this could still be running.
            return;
        }
        m_display.setCurrent(m_canvas);
        
        m_cmdNext = new Command("Next", Command.SCREEN, 10);
        m_cmdPrev = new Command("Prev", Command.SCREEN, 10);
        m_cmdSelectLevel = new Command("Select Level", Command.SCREEN, 10);
        m_cmdRestart = new Command("Restart", Command.SCREEN, 11);
        m_cmdUndo = new Command("Undo", Command.SCREEN, 1);
        m_cmdAbout = new Command("Help/About", Command.HELP, 12);
        m_cmdLicense = new Command("License", Command.HELP, 13);
        m_cmdExit = new Command("Exit", Command.EXIT, 2);
        m_canvas.addCommand(m_cmdNext);
        m_canvas.addCommand(m_cmdPrev);
        m_canvas.addCommand(m_cmdSelectLevel);
        m_canvas.addCommand(m_cmdUndo);
        m_canvas.addCommand(m_cmdRestart);
        m_canvas.addCommand(m_cmdAbout);
        m_canvas.addCommand(m_cmdLicense);
        m_canvas.addCommand(m_cmdExit);
        m_canvas.setCommandListener(this);
        m_canvas.repaint();
    }

    /**
     * Clean up before exiting.
     */
    private void cleanup()
    {
        writeLevel();
    }
    
    /**
     * Set the level. This can be used to specify an absolute level, or an
     * offset relative to the current level. (So you could, for example go
     * to the previous level by calling setLevel(-1, true).
     * 
     * @param level, the new level (or offset).
     * @param isOffset true if the level argument should be treated as an 
     * offset relative to the current one. 
     */
    public void setLevel(int newLevel, boolean isOffset)
    {
        if (isOffset)
        {
            newLevel += m_level;
        }
        
        try
        {
            m_board.read(newLevel);
        }
        catch (IOException e)
        {
            System.err.println("Couldn't load level " + newLevel);
            e.printStackTrace();
            return;
        }
        m_level = newLevel;
        m_moveList.removeAllElements();
        m_canvas.repaint();
    }

    /**
     * The listener method for game commands.
     */
    public void commandAction(Command src, Displayable d)
    {
        if (src == m_cmdNext)
        {
            setLevel(1, true);
        }
        else if (src == m_cmdPrev)
        {
            setLevel(-1, true);
        }
        else if (src == m_cmdSelectLevel)
        {
            if (m_selectLevelDialog == null)
            {
                m_selectLevelDialog = new SelectLevelDialog(this);
            }
            m_selectLevelDialog.show();
        }
        else if (src == m_cmdUndo)
        {
            undoMove();
        }
        else if (src == m_cmdRestart)
        {
            setLevel(m_level, false);
        }
        else if (src == m_cmdAbout)
        {
                doAbout();
        }
        else if (src == m_cmdLicense)
        {
                doShowLicense();
        }
        else if (src == m_cmdExit)
        {
            cleanup();
            notifyDestroyed();
        }
    }
    
    /**
     * Get the current level.
     * @return The current level.
     */
    public int getLevel()
    {
        return m_level;
    }

    /**
     * Get the board object for this game.
     * @return A board object.
     */
    public Board getBoard()
    {
        return m_board;
    }

    /**
     * Perform a game move.
     * @param move The move to perform.
     */
    public void doMove(Move move)
    {
        boolean moveOk = m_board.move(move);
        if (moveOk)
        {
            m_moveList.addElement(move);
            m_canvas.repaint();
        }
        if (m_board.isSolved())
        {
            setLevel(1, true);
        }
    }

    /**
     * Undo the last move done.
     */
    public void undoMove()
    {
        if (m_moveList.size() > 0)
        {
            Move move = (Move)m_moveList.lastElement();
            m_moveList.setSize(m_moveList.size() - 1);
            m_board.undoMove(move);
            m_canvas.repaint();
        }
    }
    
    /**
     * Read the level from the store.
     */
    private void readLevel()
    {
        int level = 1;
        RecordStore store = null;
        try
        {
            store = 
                RecordStore.openRecordStore(RECORD_STORE_NAME, false);
            // Big inidian.
            byte[] recordData = store.getRecord(1);
            if (recordData.length >= 4)
            {
                level = 0;
                for (int curByte = 0; curByte < 4; curByte++)
                {
                    level = level | (recordData[curByte] << (8 * curByte));
                }
            }
            store.closeRecordStore();
        }
        catch (RecordStoreException e)
        {
            e.printStackTrace();
        }
        System.out.println("Read level " + level);
        m_level = level;
    }
    
    /**
     * Write the level to the store. 
     */
    private void writeLevel()
    {
        RecordStore store = null;
        try
        {
            int level = m_level;
            byte[] recordData = new byte[4];
            // Big inidian.
            for (int curByte = 0; curByte < 4; curByte++)
            {
                recordData[curByte] = (byte)(level & 0xff);
                level >>= 8;
            }
            System.out.println("Opening store");
            store = 
                RecordStore.openRecordStore(RECORD_STORE_NAME, true);
            System.out.println("store.getNumRecords returend " + store.getNumRecords());
            if (store.getNumRecords() == 0)
            {
                store.addRecord(recordData, 0, recordData.length);
            }
            else
            {
                store.setRecord(1, recordData, 0, recordData.length);
            }
            store.closeRecordStore();
        }
        catch (RecordStoreException e)
        {
            e.printStackTrace();
        }
    }

    /**
     * Show the 'about' dialog.
     */
    private void doAbout()
    {
        TextView aboutDialog = new TextView(ABOUT_TITLE, ABOUT_FILE, m_display);
        aboutDialog.show();
    }

    /**
     * Show the 'license' dialog.
     */
    private void doShowLicense()
    {
        TextView licenseDialog = 
            new TextView(LICENSE_TITLE, LICENSE_FILE, m_display);
        licenseDialog.show();
    }
}
